/**
 *  @file ArrayT.h
 */
#ifndef _ARRAYT_H_
#define _ARRAYT_H_

#include "../common/BaseObject.h"
#include "../common/CppMemPool.h"
#include "../exceptions/OutOfBoundsException.h"
#include "ICollection.h"
#include "sort/SortAlgorithm.h"

namespace cppflib
{

namespace collections
{


/**
 *  Return adjusted length for copying
 */
inline u32_t __AdjustCopyLength(u32_t orgLen, u32_t srcSize, u32_t srcIndex, u32_t destSize, u32_t destIndex)
{
   if (orgLen == 0 || destIndex >= destSize || srcIndex >= srcSize)
      return 0;
       
   if (orgLen > srcSize - srcIndex)
      orgLen = srcSize - srcIndex;

   if (orgLen > destSize - destIndex)
      orgLen = destSize - destIndex;

   return orgLen;
}


/**
 *   General Array template
 *
 *   @note TYPE cannot be a C++ class but only primitive types such as 'char', 'int', ...
 */
template <class TYPE>
class ArrayT : public BaseObject, public ICollection
{
private:
    u32_t size;
    TYPE *pData;
    
    /**
     *  Make default ctor private
     */
    ArrayT(void)
    {
       size = 0;
       pData = NULL;
    }

public:
    typedef util::BaseFuncPtr2T<int, TYPE, TYPE> CmpFunc;  ///< for method declaration
    typedef util::FuncPtr2T<int, TYPE, TYPE> CmpFuncS; ///< for actual variable declaration

    typedef util::BaseFuncPtr2T<bool, int, TYPE&> ForEachFunc;
    typedef util::FuncPtr2T<bool, int, TYPE&> ForEachFuncS;
    
private:
    /**
     *  This one is used internally -- casting data to original type before
     *  passing to user provided function for comparison. 
     */
    class PrivateSortCmpFunc : public sort::SortCmpFunc
    {
    private:
       const CmpFunc &orgCmpFunc;
    private: 
       PrivateSortCmpFunc() { } 
    public: 
       explicit PrivateSortCmpFunc(const CmpFunc &f) : orgCmpFunc(f) { } 
       virtual ~PrivateSortCmpFunc() { } 
       virtual int operator() (ptrany_t a1, ptrany_t a2) const 
       { 
          TYPE a = *reinterpret_cast<TYPE*>(a1);
          TYPE b = *reinterpret_cast<TYPE*>(a2);
          return orgCmpFunc(a, b); 
       } 
    };

public:
    explicit ArrayT(u32_t initSize)
    {
       size = initSize;
       pData = static_cast<TYPE*>(::MRP_Calloc(size, sizeof(TYPE)));
    }

    virtual ~ArrayT(void)
    {
       if (pData)
          cppmempool::Release(pData);
    }

    /**
     *  Return a reference so that it can be a l-value or r-value
     */
    TYPE& operator[] (int nIndex)
    {
       if (nIndex < 0 || static_cast<u32_t>(nIndex) >= size)
          throw exceptions::OutOfBoundsException(_S("Array index out of bound"), -1);

       return pData[nIndex];
    }

    u32_t GetSize()
    {
       return size;
    }

    TYPE *GetRawPtr()
    {
       return pData;
    }

    void Clear()
    {
       if (size == 0 || !pData)
          return;

       for (u32_t i = 0; i < size; ++i)
          pData[i] = 0;
    }

    /**
     *  Copy elements from current array to another array
     */
    bool CopyTo(ArrayT<TYPE> &destArray, u32_t curArrayIndex, u32_t destArrayIndex, u32_t len)
    {
       return CopyTo(destArray.GetRawPtr(), destArray.GetSize(), curArrayIndex, destArrayIndex, len);
    }

    /**
     *  Copy elements from current array to another array
     */
    bool CopyTo(ArrayT<TYPE> &destArray, u32_t len)
    {
       return CopyTo(destArray.GetRawPtr(), destArray.GetSize(), 0, 0, len);
    }

    /**
     *  Copy elements from current array to a buffer pointed to by a raw pointer
     * 
     *  @param [in, out] destPtr -- raw pointer to destination buffer
     *  @param [in] destPtrSize -- total number of elements pointed to by destPtr
     *  @param [in] curArrayIndex -- starting index of current array to copy
     *  @param [in] destPtrIndex -- starting index of destination buffer to copy
     *  @param [in] len -- number of elements to copy
     *
     *  @return true if length copied same as requested
     */
    bool CopyTo(TYPE *destPtr, u32_t destPtrSize, u32_t curArrayIndex, u32_t destPtrIndex, u32_t len)
    {
       u32_t adjustedLen = __AdjustCopyLength(len, this->size, curArrayIndex, destPtrSize, destPtrIndex);
       if (adjustedLen == 0)
          return false;

       TYPE *pDest = destPtr + destPtrIndex;
       TYPE *pSrc = this->pData + curArrayIndex;

       for (u32_t i = 0 ; i < adjustedLen; ++i)
          *pDest++ = *pSrc++;

       return (adjustedLen == len);
    }

    /**
     *  Copy elements from another array to current array
     */
    bool CopyFrom(ArrayT<TYPE> &srcArray, u32_t curArrayIndex, u32_t srcArrayIndex, u32_t len)
    {
       return CopyFrom(srcArray.GetRawPtr(), srcArray.GetSize(), curArrayIndex, srcArrayIndex, len);
    }

    /**
     *  Copy elements from another array to current array
     */
    bool CopyFrom(ArrayT<TYPE> &srcArray, u32_t len)
    {
       return CopyFrom(srcArray.GetRawPtr(), srcArray.GetSize(), 0, 0, len);
    }

    /**
     *  Copy elements from another array to current array
     *
     *  @param [in] srcPtr -- raw pointer to source buffer
     *  @param [in] srcPtrSize -- total number of elements pointed to by srcPtr
     *  @param [in] curArrayIndex -- starting index of current array to copy
     *  @param [in] srcPtrIndex -- starting index of source buffer to copy
     *  @param [in] len -- number of elements to copy
     *
     *  @return true if length copied same as requested
     */
    bool CopyFrom(TYPE *srcPtr, u32_t srcPtrSize, u32_t curArrayIndex, u32_t srcPtrIndex, u32_t len)
    {
       u32_t adjustedLen = __AdjustCopyLength(len, srcPtrSize, srcPtrIndex, this->size, curArrayIndex);
       if (adjustedLen == 0)
          return false;

       TYPE *pDest = this->pData + curArrayIndex;
       TYPE *pSrc = srcPtr + srcPtrIndex;

       for (u32_t i = 0 ; i < adjustedLen; ++i)
          *pDest++ = *pSrc++;

       return (adjustedLen == len);
    }

    /**
     *  Resize the array, which can be bigger or smaller than original one.
     */
    void Resize(u32_t newSize)
    {
       if (size == newSize)
          return;

       pData = static_cast<TYPE*>(::MRP_Realloc(pData, newSize * sizeof(TYPE)));

       if (newSize > size) {
          // ensure new slots are 0
          for (u32_t i = size; i < newSize; ++i) {
             pData[i] = static_cast<TYPE>(0);
          }
       }

       size = newSize;
    }

    /**
     *  Swap element of 2 slots
     *  @implements ICollection::Swap
     */
    virtual void Swap(int i_1, int i_2)
    {
       if (i_1 < 0 || i_1 >= static_cast<int>(size) ||
           i_2 < 0 || i_2 >= static_cast<int>(size))
          throw exceptions::OutOfBoundsException(_S("Array index out of bound"), -1);

       // exchange value
       TYPE tempVal = pData[i_1];
       pData[i_1] = pData[i_2];
       pData[i_2] = tempVal;
    }
    /**
     *  Retrieve ith pointer to the element  
     *  @implements ICollection::Element
     */
    virtual ptrany_t Element(int i)
    {
       if (i < 0 || i >= static_cast<int>(size))
          throw exceptions::OutOfBoundsException(_S("Array index out of bound"), -1);

       return reinterpret_cast<ptrany_t>(&(pData[i]));
    }

    /**
     *  Sort all array elements
     */
    void Sort(const CmpFunc &cmpFunc)
    {
       PrivateSortCmpFunc privateFunc(cmpFunc);
       sort::HeapSort(*this, 0, static_cast<int>(this->GetSize()), privateFunc);
    }

    /**
     *  Sort partial array elements
     */
    void Sort(int startIndex, int count, const CmpFunc &cmpFunc)
    {
       PrivateSortCmpFunc privateFunc(cmpFunc);
       sort::HeapSort(*this, startIndex, count, privateFunc);
    }

    /**
     *  Pass each item to a user provided function
     */
    void ForEach(const ForEachFunc &forEachFunc)
    {
       for (int i = 0; i < static_cast<int>(size); ++i) {
          if (! forEachFunc(i, pData[i])) // break if return false
             break;
       }
    }
    
};

/**
 *  Template for an array of pointers to C++ class derived from 'BaseObject'
 */
template <class TYPE>
class ArrayCppObjT : public BaseObject, public ICollection
{ 
    
private:
    u32_t size;
    BaseObject **pData;

    /**
     *  Make default ctor private
     */
    ArrayCppObjT(void)
    {
       size = 0;
       pData = NULL;
    }

public:
    typedef util::BaseFuncPtr2T<int, TYPE*, TYPE*> CmpFunc;
    typedef util::FuncPtr2T<int, TYPE*, TYPE*> CmpFuncS;

    typedef util::BaseFuncPtr2T<bool, int, TYPE*&> ForEachFunc;
    typedef util::FuncPtr2T<bool, int, TYPE*&> ForEachFuncS;
    
private:
    /**
     *  This one is used internally -- casting data to original type before
     *  passing to user provided function for comparison. 
     */
    class PrivateSortCmpFunc : public sort::SortCmpFunc
    {
    private:
       const CmpFunc &orgCmpFunc;
    private: 
       PrivateSortCmpFunc() { } 
    public: 
       explicit PrivateSortCmpFunc(const CmpFunc &f) : orgCmpFunc(f) { } 
       virtual ~PrivateSortCmpFunc() { } 
       virtual int operator() (ptrany_t a1, ptrany_t a2) const 
       { 
          TYPE* a = reinterpret_cast<TYPE*>(a1);
          TYPE* b = reinterpret_cast<TYPE*>(a2);
          return orgCmpFunc(a, b); 
       } 
    };

public:
    explicit ArrayCppObjT(u32_t initSize)
    {
       size = initSize;
       pData = static_cast<BaseObject**>(::MRP_Calloc(size, sizeof(BaseObject*)));
    }

    virtual ~ArrayCppObjT(void)
    {
       if (pData) {
          Clear();
          cppmempool::Release(pData);
       }
    }

/*
    // Use "Get", "Set", "Remove" below instead of this operator so that retain and release
    // can be performed automatically.

    TYPE*& operator[] (int nIndex)
    {
       return pData[nIndex];
    }
*/

    /**
     *  Set a new entry without removing previous one
     */
    void DirectSet(int nIndex, TYPE *ptr)
    {
       if (nIndex < 0 || static_cast<u32_t>(nIndex) >= size)
          throw exceptions::OutOfBoundsException(_S("Array index out of bound"), -1);

       cppmempool::Retain(ptr);  // increase ref count 
       pData[nIndex] = ptr;
    }

    /**
     *  Remove original entry first before setting a new one
     */
    void Set(int nIndex, TYPE *ptr)
    {
       if (nIndex < 0 || static_cast<u32_t>(nIndex) >= size)
          throw exceptions::OutOfBoundsException(_S("Array index out of bound"), -1);

       cppmempool::Retain(ptr);  // increase ref count 
       Remove(nIndex);  // remove original slot if something exists  
       pData[nIndex] = ptr;
    }

    TYPE* Get(int nIndex)
    {
       if (nIndex < 0 || static_cast<u32_t>(nIndex) >= size)
          throw exceptions::OutOfBoundsException(_S("Array index out of bound"), -1);

       return static_cast<TYPE*>(pData[nIndex]);
    }

    void Remove(int nIndex)
    {
       if (nIndex < 0 || static_cast<u32_t>(nIndex) >= size)
          throw exceptions::OutOfBoundsException(_S("Array index out of bound"), -1);

       if (pData[nIndex]) {
          cppmempool::Release(pData[nIndex]);
          pData[nIndex] = NULL;
       }
    }

    u32_t GetSize()
    {
       return size;
    }

    void Clear()
    {
       if (size == 0 || !pData)
          return;

       // release element one by one
       for (u32_t i = 0 ; i < size; ++i) {
          Remove(i);
       }
    }

    /**
     *  Copy elements from current array to another array
     *
     *  @return false if part of or no source elements are copied to destination
     */
    bool CopyTo(ArrayCppObjT<TYPE> &destArray, u32_t len)
    {
       return CopyTo(destArray, 0, 0, len);
    }

    bool CopyTo(ArrayCppObjT<TYPE> &destArray, u32_t curArrayIndex, u32_t destIndex, u32_t len)
    {
       u32_t adjustedLen = __AdjustCopyLength(len, this->size, curArrayIndex, destArray.GetSize(), destIndex);
       if (adjustedLen == 0)
          return false;

       BaseObject **pSrc = this->pData + curArrayIndex;
       
       for (u32_t i = 0 ; i < adjustedLen; ++i) {
          destArray.Set(destIndex + i, static_cast<TYPE*>(*pSrc++));
       }

       return (adjustedLen == len);
    }

    /**
     *  Copy elements from another array to current array
     */
    bool CopyFrom(ArrayCppObjT<TYPE> &srcArray, u32_t len)
    {
       return CopyFrom(srcArray, 0, 0, len);
    }

    bool CopyFrom(ArrayCppObjT<TYPE> &srcArray, u32_t curArrayIndex, u32_t srcArrayIndex, u32_t len)
    {
       u32_t adjustedLen = __AdjustCopyLength(len, srcArray.GetSize(), srcArrayIndex, this->size, curArrayIndex);
       if (adjustedLen == 0)
          return false;

       BaseObject **pSrc = srcArray.pData + srcArrayIndex;
       
       for (u32_t i = 0 ; i < adjustedLen; ++i) {
          this->Set(curArrayIndex + i, static_cast<TYPE*>(*pSrc++));
       }

       return (adjustedLen == len);
    } 

    /**
     *  Resize the array, which can be bigger or smaller than original one.
     */
    void Resize(u32_t newSize)
    {
       if (size == newSize)
          return;

       if (newSize < size) {
          // remove original slots if new size is smaller
          for (u32_t i = newSize; i < size; ++i) {
             Remove(i);
          }
       }

       pData = static_cast<BaseObject**>(::MRP_Realloc(pData, newSize * sizeof(BaseObject*)));

       if (newSize > size) {
          // ensure new slots are NULL
          for (u32_t i = size; i < newSize; ++i) {
             pData[i] = NULL;
          }
       }

       size = newSize;
    }

    /**
     *  Swap element of 2 slots
     *  @implements ICollection::Swap
     */
    virtual void Swap(int i_1, int i_2)
    {
       if (i_1 < 0 || i_1 >= static_cast<int>(size) ||
           i_2 < 0 || i_2 >= static_cast<int>(size))
          throw exceptions::OutOfBoundsException(_S("Array index out of bound"), -1);

       // exchange value
       BaseObject * pTempVal = pData[i_1];
       pData[i_1] = pData[i_2];
       pData[i_2] = pTempVal;
    }
    /**
     *  Retrieve ith pointer to the element  
     *  @implements ICollection::Element
     */
    virtual ptrany_t Element(int i)
    {
       if (i < 0 || i >= static_cast<int>(size))
          throw exceptions::OutOfBoundsException(_S("Array index out of bound"), -1);

       return reinterpret_cast<ptrany_t>(pData[i]);
    }

    /**
     *  Sort all array elements
     */
    void Sort(const CmpFunc &cmpFunc)
    {
       PrivateSortCmpFunc privateFunc(cmpFunc);
       sort::HeapSort(*this, 0, static_cast<int>(this->GetSize()), privateFunc);
    }

    /**
     *  Sort partial array elements
     */
    void Sort(int startIndex, int count, const CmpFunc &cmpFunc)
    {
       PrivateSortCmpFunc privateFunc(cmpFunc);
       sort::HeapSort(*this, startIndex, count, privateFunc);
    }

    /**
     *  Pass each item to a user provided function
     */
    void ForEach(const ForEachFunc &forEachFunc)
    {
       for (int i = 0; i < static_cast<int>(size); ++i) {
          TYPE* p = static_cast<TYPE*>(pData[i]);
          if (! forEachFunc(i, p)) // break if return false
             break;
       }
    }
};


/**
 *  Declare some array factory type
 */
typedef ArrayT<char> ArrayChar;
typedef ArrayT<u8_t> ArrayByte;
typedef ArrayT<wc_t> ArrayWChar;
typedef ArrayT<short> ArrayShort;
typedef ArrayT<u16_t> ArrayUShort;
typedef ArrayT<int> ArrayInt;
typedef ArrayT<u32_t> ArrayUInt;
typedef ArrayT<long> ArrayLong;
typedef ArrayT<unsigned long> ArrayULong;
typedef ArrayT<i64_t> ArrayInt64;
typedef ArrayT<u64_t> ArrayUInt64;
typedef ArrayT<bool> ArrayBool;
typedef ArrayT<float> ArrayFloat;
typedef ArrayT<double> ArrayDouble;
typedef ArrayT<ptr_t> ArrayPtr;


} // end of namespace collections


} // end of namespace cppflib

#endif
